<!--
  Cesium 3D 地球场景 - 直升机飞行动画演示
  功能：
  1. 加载 Cesium 3D 地球和地形
  2. 显示全球建筑物（OSM 数据）
  3. 直升机模型沿圆形路径飞行
  4. 点击建筑物高亮显示
  5. 相机自动跟踪直升机
  6. 显示飞行轨迹线
-->
<template>
	<!-- Cesium 容器 -->
	<div id="cesium-dom" class="cesium-content"></div>
</template>

<script setup lang="ts">
// ==================== 导入依赖 ====================
import * as cesium from "cesium"; // Cesium 3D 地球库
import "@cesium/widgets/Source/widgets.css"; // Cesium 样式
import model from "@/model/bell206_helicopter.glb"; // 直升机 3D 模型

// ==================== Cesium Ion 配置 ====================
// Cesium Ion 访问令牌，用于访问 Cesium 的在线资源（地形、影像、建筑物等）
cesium.Ion.defaultAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0OTBmMzllMy0yYjgzLTQ1YmUtYjNlNi1iOGRhODdhYzkzODMiLCJpZCI6MzYxNjQyLCJpYXQiOjE3NjM1MTgzMTd9.cPtinfeWhfpRWc7Phcm--JhCSqF4pdBcVUDbi4bhvPI";

// ==================== 组件挂载后初始化 ====================
onMounted(async () => {
	// -------------------- 创建 Cesium Viewer --------------------
	// Viewer 是 Cesium 的核心对象，管理整个 3D 场景
	const viewer = new cesium.Viewer("cesium-dom", {
		animation: false,        // 隐藏动画控制器
		timeline: false,         // 隐藏时间轴
		baseLayerPicker: false,  // 隐藏图层选择器
		terrain: cesium.Terrain.fromWorldTerrain({
			requestWaterMask: true,      // 请求水体遮罩（显示海洋、湖泊）
			requestVertexNormals: true,  // 请求顶点法线（用于光照计算）
		}),
	});

	// -------------------- 场景配置 --------------------
	// 启用光照效果，让场景更真实（太阳光照、阴影等）
	viewer.scene.globe.enableLighting = true;

	// ==================== 加载全球建筑物 ====================
	/**
	 * 加载 OpenStreetMap (OSM) 建筑物 3D Tiles
	 * 3D Tiles 是 Cesium 的一种 3D 数据格式，用于高效渲染大规模 3D 场景
	 * OSM 建筑物数据包含全球主要城市的建筑物模型
	 */
	try {
		const osmBuildings = await cesium.createOsmBuildingsAsync();
		viewer.scene.primitives.add(osmBuildings);
		console.log('建筑物加载成功');
	} catch (error) {
		console.warn('建筑物加载失败:', error);
	}

	// ==================== 建筑物点击交互 ====================
	/**
	 * 实现点击建筑物高亮功能
	 * 1. 点击建筑物时，将其高亮为黄色
	 * 2. 点击其他建筑物时，恢复之前建筑物的颜色
	 * 3. 在控制台输出建筑物信息（名称、高度、类型）
	 */
	// 创建屏幕空间事件处理器，用于处理鼠标、触摸等输入事件
	const handler = new cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
	// 存储当前选中的建筑物
	let selectedBuilding: cesium.Cesium3DTileFeature | null = null;

	// 监听鼠标左键点击事件
	handler.setInputAction((movement: cesium.ScreenSpaceEventHandler.PositionedEvent) => {
		// 1. 恢复之前选中的建筑颜色
		if (selectedBuilding) {
			selectedBuilding.color = cesium.Color.WHITE.withAlpha(0.6);
		}

		// 2. 拾取鼠标点击位置的对象
		const pickedFeature = viewer.scene.pick(movement.position);
		// 3. 检查是否点击到建筑物（Cesium3DTileFeature 类型）
		if (cesium.defined(pickedFeature) && pickedFeature instanceof cesium.Cesium3DTileFeature) {
			selectedBuilding = pickedFeature;
			// 4. 高亮为亮黄色
			selectedBuilding.color = cesium.Color.YELLOW;
			
			// 5. 获取并打印建筑信息
			const name = pickedFeature.getProperty('name') || '未命名建筑';
			const height = pickedFeature.getProperty('building:height') || '未知';
			const buildingType = pickedFeature.getProperty('building') || '未知类型';
			console.log(`建筑: ${name}, 高度: ${height}米, 类型: ${buildingType}`);
		}
	}, cesium.ScreenSpaceEventType.LEFT_CLICK);

	// ==================== 获取地形高度 ====================
	/**
	 * 获取指定位置的地形高度
	 * 这是为了让直升机从正确的地面高度起飞
	 * 位置：北京市中心（天安门附近）
	 */
	const longitude = 116.39;  // 经度
	const latitude = 39.9;     // 纬度

	// 创建地理坐标对象（经纬度格式）
	const cartographic = cesium.Cartographic.fromDegrees(longitude, latitude);

	// 获取该位置的地形高度
	let terrainHeight = 50; // 默认高度（北京市区平均海拔约50米）
	try {
		// 使用最详细的地形数据采样
		const terrainSamples = await cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [cartographic]);
		if (terrainSamples && terrainSamples.length > 0 && terrainSamples[0].height !== undefined) {
			terrainHeight = terrainSamples[0].height;
		}
		console.log('获取到的地形高度:', terrainHeight);
	} catch (error) {
		console.warn('获取地形高度失败，使用默认值:', error);
	}

	// ==================== 创建飞行动画时间轴 ====================
	/**
	 * 设置动画时间范围和播放模式
	 * 飞行分为两个阶段：
	 * 1. 起飞阶段：10秒，垂直上升
	 * 2. 飞行阶段：360秒（6分钟），沿圆形路径飞行
	 */
	const startTime = cesium.JulianDate.now();  // 动画开始时间（当前时间）
	const takeoffDuration = 10;   // 起飞阶段持续时间（秒）
	const flightDuration = 360;   // 飞行阶段持续时间（秒）
	const totalDuration = takeoffDuration + flightDuration;  // 总时长
	const stopTime = cesium.JulianDate.addSeconds(startTime, totalDuration, new cesium.JulianDate());

	// 配置 Cesium 时钟
	viewer.clock.startTime = startTime.clone();      // 设置开始时间
	viewer.clock.stopTime = stopTime.clone();        // 设置结束时间
	viewer.clock.currentTime = startTime.clone();    // 设置当前时间
	viewer.clock.clockRange = cesium.ClockRange.LOOP_STOP;  // 循环播放模式
	viewer.clock.multiplier = 1;  // 时间倍速（1=实时，2=2倍速）

	// ==================== 创建飞行路径 ====================
	/**
	 * 使用 SampledPositionProperty 定义直升机的飞行路径
	 * 通过在不同时间点添加位置样本，Cesium 会自动进行插值，生成平滑的飞行轨迹
	 */
	const positionProperty = new cesium.SampledPositionProperty();

	// 飞行参数配置
	const radius = 0.01;  // 圆形路径半径（约1公里，经纬度单位）
	const flightHeight = terrainHeight + 100;  // 飞行高度（地形高度 + 100米）
	const groundHeight = terrainHeight + 2;    // 起飞点高度（地形高度 + 2米）

	// -------------------- 阶段1：垂直起飞 --------------------
	/**
	 * 直升机从地面垂直上升到飞行高度
	 * 分5个步骤，每个步骤之间线性插值，形成平滑的上升动画
	 */
	const takeoffSteps = 5;  // 起飞动画分为5个关键帧
	for (let i = 0; i <= takeoffSteps; i++) {
		// 计算当前关键帧的时间
		const time = cesium.JulianDate.addSeconds(startTime, (i / takeoffSteps) * takeoffDuration, new cesium.JulianDate());
		// 计算当前关键帧的高度（线性插值）
		const height = groundHeight + (flightHeight - groundHeight) * (i / takeoffSteps);
		// 创建位置（经度、纬度、高度）
		const position = cesium.Cartesian3.fromDegrees(longitude, latitude, height);
		// 添加位置样本到路径
		positionProperty.addSample(time, position);
	}

	// -------------------- 阶段2：圆形飞行 --------------------
	/**
	 * 直升机沿着圆形路径飞行
	 * 使用三角函数（cos, sin）计算圆形路径上的点
	 * 每10秒采样一个点，共360秒完成一圈
	 */
	const flightStartTime = cesium.JulianDate.addSeconds(startTime, takeoffDuration, new cesium.JulianDate());
	for (let i = 0; i <= flightDuration; i += 10) {
		// 计算当前时间
		const time = cesium.JulianDate.addSeconds(flightStartTime, i, new cesium.JulianDate());
		// 计算当前角度（0到2π，完整一圈）
		const angle = (i / flightDuration) * Math.PI * 2;

		// 使用圆的参数方程计算位置
		// x = centerX + radius * cos(angle)
		// y = centerY + radius * sin(angle)
		const lon = longitude + radius * Math.cos(angle);
		const lat = latitude + radius * Math.sin(angle);
		const position = cesium.Cartesian3.fromDegrees(lon, lat, flightHeight);

		// 添加位置样本
		positionProperty.addSample(time, position);
	}

	// -------------------- 设置插值算法 --------------------
	/**
	 * 设置位置插值方式
	 * LinearApproximation：线性插值，使运动更平滑
	 * interpolationDegree：插值度数，1表示线性插值
	 */
	positionProperty.setInterpolationOptions({
		interpolationDegree: 1,
		interpolationAlgorithm: cesium.LinearApproximation,
	});

	// ==================== 创建模型朝向 ====================
	/**
	 * 让直升机模型始终朝向运动方向
	 * VelocityOrientationProperty 会根据速度向量自动计算朝向
	 * 但由于模型本身的朝向可能不对，需要添加旋转偏移来修正
	 */
	// 基于速度的自动朝向
	const velocityOrientation = new cesium.VelocityOrientationProperty(positionProperty);

	// -------------------- 朝向偏移参数 --------------------
	/**
	 * 三个旋转角度（欧拉角）：
	 * - heading: 航向角，绕Z轴旋转（水平方向，0度=正北，90度=正东）
	 * - pitch: 俯仰角，绕Y轴旋转（上下倾斜，正值=抬头，负值=低头）
	 * - roll: 翻滚角，绕X轴旋转（左右倾斜，正值=右倾，负值=左倾）
	 */
	const headingOffset = cesium.Math.toRadians(110);  // 水平旋转110度（修正模型默认朝向）
	const pitchOffset = cesium.Math.toRadians(0);      // 俯仰角0度（保持水平）
	const rollOffset = cesium.Math.toRadians(0);       // 翻滚角0度（保持水平）

	// -------------------- 自定义朝向计算 --------------------
	/**
	 * 使用 CallbackProperty 动态计算模型朝向
	 * 每一帧都会调用这个回调函数，返回当前时刻的朝向（四元数）
	 */
	const customOrientation = new cesium.CallbackProperty((time, result) => {
		// 1. 获取基于速度的自动朝向
		const velocity = velocityOrientation.getValue(time, result);
		if (velocity) {
			const currentPosition = positionProperty.getValue(time);
			if (currentPosition) {
				// 2. 创建旋转偏移（欧拉角 -> 四元数）
				const hpr = new cesium.HeadingPitchRoll(headingOffset, pitchOffset, rollOffset);
				const offset = cesium.Transforms.headingPitchRollQuaternion(
					currentPosition,
					hpr
				);
				// 3. 将速度朝向和偏移相乘，得到最终朝向
				return cesium.Quaternion.multiply(velocity, offset, result);
			}
		}
		return velocity;
	}, false);

	// ==================== 创建直升机实体 ====================
	/**
	 * Entity 是 Cesium 中的实体对象，可以包含模型、位置、朝向等属性
	 * 这里创建一个包含直升机模型的实体，并设置其飞行路径和轨迹线
	 */
	const entity = viewer.entities.add({
		// 设置实体的可见时间范围
		availability: new cesium.TimeIntervalCollection([
			new cesium.TimeInterval({
				start: startTime,
				stop: stopTime,
			}),
		]),
		// 设置实体的位置（随时间变化）
		position: positionProperty,
		// 设置实体的朝向（随时间变化）
		orientation: customOrientation,
		// 设置3D模型
		model: {
			uri: model,              // 模型文件路径
			minimumPixelSize: 64,    // 最小像素大小（防止模型太小看不见）
			maximumScale: 200,       // 最大缩放比例
			show: true,              // 显示模型
		},
		// 添加飞行轨迹线
		path: {
			resolution: 1,           // 路径分辨率（越小越平滑）
			material: new cesium.PolylineGlowMaterialProperty({
				glowPower: 0.3,      // 发光强度
				color: cesium.Color.CYAN,  // 轨迹颜色（青色）
			}),
			width: 8,                // 线宽（像素）
			leadTime: 0,             // 前方轨迹时间（0=不显示前方轨迹）
			trailTime: 360,          // 历史轨迹时间（360秒=显示完整轨迹）
		},
	});

	console.log('模型位置:', longitude, latitude, '地形高度:', terrainHeight);

	// ==================== 相机跟踪 ====================
	/**
	 * 让相机自动跟踪直升机
	 * trackedEntity 会让相机始终跟随实体移动，并自动调整视角
	 */
	setTimeout(() => {
		// 方式1: 使用 trackedEntity 自动跟踪（推荐）
		viewer.trackedEntity = entity;

		// 方式2: 或者使用 zoomTo 跟踪（可以自定义视角）
		// viewer.zoomTo(entity, new cesium.HeadingPitchRange(0, cesium.Math.toRadians(-30), 300));

		console.log('相机开始跟踪模型，模型正在沿圆形路径飞行');
	}, 100);

	// 启动时钟动画
	viewer.clock.shouldAnimate = true;
});
</script>

<style>
/* Cesium 容器样式 */
.cesium-content {
	width: 100%;
	height: 600px;
	background-color: red;
}
</style>
